package com.yunhe.common.excel;


import com.yunhe.common.exception.ArgumentErrorException;
import com.yunhe.common.util.StringUtil;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.poi.ss.usermodel.CellType.STRING;

/**
 * 功能: [POI实现把Excel数据导入到数据库] 作者: JML 版本: 1.0
 */
public class ExcelImport {

    // 正则表达式 用于匹配属性的第一个字母
    private static final String REGEX = "[a-zA-Z]";

    /**
     * 功能: Excel数据导入到数据库 参数: file 文件 参数: startRow[从第几行开始] 参数: endRow[到第几行结束
     * (0表示所有行; 正数表示到第几行结束; 负数表示到倒数第几行结束)] 参数: clazz[要返回的对象集合的类型]
     */
    public List<?> importExcel(MultipartFile file, int startRow, int endRow,
                               Class<?> clazz) throws IOException, ArgumentErrorException {
        // 是否打印提示信息
        boolean showInfo = false;
        return doImportExcel(file, startRow, endRow, showInfo, clazz);
    }

    /**
     * 功能:真正实现导入
     *
     * @throws IOException
     */
    private List<Object> doImportExcel(MultipartFile file, int startRow,
                                       int endRow, boolean showInfo, Class<?> clazz) throws IOException, ArgumentErrorException {
        // InputStream fis=null;
        List<Row> rowList = new ArrayList<Row>();
        // fis = new FileInputStream(file);
        // 去读Excel
        Workbook workbook = ExcelBase.getWorkBook(file.getInputStream(),
                file.getOriginalFilename());

        if (workbook != null) {
            Sheet sheet = workbook.getSheetAt(0);
            // 获取最后行号
            int lastRowNum = sheet.getPhysicalNumberOfRows();//sheet.getLastRowNum();
            if (lastRowNum > 0) { // 如果>0，表示有数据
                out("\n开始读取名为【" + sheet.getSheetName() + "】的内容：", showInfo);
            }

            for (int i = startRow; i <=lastRowNum ; i++) {// 获取每行
                Row row = sheet.getRow(i);
                if (ExcelBase.isBlankRow(row)) {//过滤空行
                    continue;
                }
                rowList.add(row);//主要是这行,其他行无视
                out("第" + (i + 1) + "行：", showInfo, false);
                // 获取每一单元格的值
                for (int j = 0; j < row.getLastCellNum(); j++) {
                    String value = ExcelBase.getCellValue(row.getCell(j));
                    if (!value.equals("")) {
                        out(value + " | ", showInfo, false);
                    }
                }
                out("", showInfo);
            }
            // 如果是HSSFWorkbook 关闭, XSSFWorkbook自动释放
//            if (workbook instanceof HSSFWorkbook) {
//                workbook.close();
//            }
        }
        //****************关键方法***************
        return returnObjectList(rowList, clazz);
    }

    /**
     * 功能:获取单元格的值    这个不要用了(不全),用  ExcelBase.getCellValue(row.getCell(j));
     */
    public String getCellValue(Cell cell) {
        Object result = "";
        if (cell != null) {
            switch (cell.getCellType()) {
                case STRING:
                    result = cell.getStringCellValue();
                    break;
                case NUMERIC:
                    result = cell.getNumericCellValue();
                    break;
                case BOOLEAN:
                    result = cell.getBooleanCellValue();
                    break;
                case FORMULA:
                    result = cell.getCellFormula();
                    break;
                case ERROR:
                    result = cell.getErrorCellValue();
                    break;
                case BLANK:
                    break;
                default:
                    break;
            }
        }
        return result.toString();
    }

    /**
     * 功能:返回指定的对象集合
     */
    private List<Object> returnObjectList(List<Row> rowList, Class<?> clazz) {
        List<Object> objectList = null;
        Object obj = null;
        String attribute = null;
        String value = null;
        int j = 0;
        try {
            objectList = new ArrayList<Object>();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Row row : rowList) {
                j = 0;
                obj = clazz.newInstance();
                for (Field field : declaredFields) {
                    attribute = field.getName().toString();
                    value = ExcelBase.getCellValue(row.getCell(j));
                    setAttrributeValue(obj, attribute, value);
                    j++;
                }
                objectList.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return objectList;
    }

    /**
     * 功能:给指定对象的指定属性赋值
     */
    private void setAttrributeValue(Object obj, String attribute, String value) {
        // 得到该属性的set方法名
        String method_name = convertToMethodName(attribute, obj.getClass(),
                true);
        Method[] methods = obj.getClass().getMethods();
        for (Method method : methods) {
            /**
             * 因为这里只是调用bean中属性的set方法，属性名称不能重复 所以set方法也不会重复，所以就直接用方法名称去锁定一个方法
             * （注：在java中，锁定一个方法的条件是方法名及参数）
             */
            if (method.getName().equals(method_name)) {
                Class<?>[] parameterC = method.getParameterTypes();
                try {
                    /**
                     * 如果是(整型,浮点型,布尔型,字节型,时间类型), 按照各自的规则把value值转换成各自的类型
                     * 否则一律按类型强制转换(比如:String类型)
                     */
                    if (parameterC[0] == int.class
                            || parameterC[0] == Integer.class) {
                        value = value.substring(0, value.lastIndexOf("."));
                        method.invoke(obj, Integer.valueOf(value));
                        break;
                    } else if (parameterC[0] == float.class
                            || parameterC[0] == Float.class) {
                        method.invoke(obj, Float.valueOf(value));
                        break;
                    } else if (parameterC[0] == double.class
                            || parameterC[0] == Double.class) {
                        method.invoke(obj, Double.valueOf(value));
                        break;
                    } else if (parameterC[0] == byte.class
                            || parameterC[0] == Byte.class) {
                        method.invoke(obj, Byte.valueOf(value));
                        break;
                    } else if (parameterC[0] == BigDecimal.class
                            || parameterC[0] == BigDecimal.class) {
                        if(!StringUtil.isEmpty(value)){
                            method.invoke(obj, new BigDecimal(value));
                        }
                        break;
                    } else if (parameterC[0] == boolean.class
                            || parameterC[0] == Boolean.class) {
                        method.invoke(obj, Boolean.valueOf(value));
                        break;
                    } else if (parameterC[0] == Date.class) {
                        SimpleDateFormat sdf =null;
                        if(value.indexOf("/")>-1){
                            sdf=new SimpleDateFormat("yyyy/MM/dd");
                        }else{
                            sdf=new SimpleDateFormat("yyyy-MM-dd");
                        }
                        Date date = null;
                        try {
                            date = sdf.parse(value);
                        } catch (Exception e) {
                            throw new ArgumentErrorException("日期格式转换异常,可以尝试对单元格格式设置日期格式");
                        }
                        method.invoke(obj, date);
                        break;
                    } else {
                        method.invoke(obj, parameterC[0].cast(value));
                        break;
                    }
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (SecurityException | ArgumentErrorException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 功能:根据属性生成对应的set/get方法
     */
    private String convertToMethodName(String attribute, Class<?> objClass,
                                       boolean isSet) {
        /** 通过正则表达式来匹配第一个字符 **/
        Pattern p = Pattern.compile(REGEX);
        Matcher m = p.matcher(attribute);
        StringBuilder sb = new StringBuilder();
        /** 如果是set方法名称 **/
        if (isSet) {
            sb.append("set");
        } else {
            /** get方法名称 **/
            try {
                Field attributeField = objClass.getDeclaredField(attribute);
                /** 如果类型为boolean **/
                if (attributeField.getType() == boolean.class
                        || attributeField.getType() == Boolean.class) {
                    sb.append("is");
                } else {
                    sb.append("get");
                }
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
        /** 针对以下划线开头的属性 **/
        if (attribute.charAt(0) != '_' && m.find()) {
            sb.append(m.replaceFirst(m.group().toUpperCase()));
        } else {
            sb.append(attribute);
        }
        return sb.toString();
    }

    /**
     * 功能:输出提示信息(普通信息打印)
     */
    private void out(String info, boolean showInfo) {
        if (showInfo) {
            System.out.print(info + (showInfo ? "\n" : ""));
        }
    }

    /**
     * 功能:输出提示信息(同一行的不同单元格信息打印)
     */
    private void out(String info, boolean showInfo, boolean nextLine) {
        if (showInfo) {
            if (nextLine) {
                System.out.print(info + (showInfo ? "\n" : ""));
            } else {
                System.out.print(info);
            }
        }
    }
}